/*
 *  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
 *  reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following discalimer in
 *  the documentation and/or other materials provided with the
 *  distribution.
 *
 *  3. The end-user documentation included with the redistribution,
 *  if any, must include the following acknowledgment:
 *  "This product includes software developed by the
 *  Sun Microsystems, Inc. for Project JXTA."
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *  must not be used to endorse or promote products derived from this
 *  software without prior written permission. For written
 *  permission, please contact Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA",
 *  nor may "JXTA" appear in their name, without prior written
 *  permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many
 *  individuals on behalf of Project JXTA.  For more
 *  information on Project JXTA, please see
 *  <http://www.jxta.org/>.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 *
 *  $Id: DialogManager.java,v 1.29 2007/06/10 21:15:10 nano Exp $
 */

package net.jxta.myjxta.dialog;

import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.misc.beam.BeamDialog;
import net.jxta.myjxta.util.Group;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeService;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.util.JxtaBiDiPipe;

import java.util.*;

/**
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: DialogManager.java,v 1.29 2007/06/10 21:15:10 nano Exp $
 * @modified 2005-03-27 jamoore add vojxtaDialog to dialog types
 * @modified 2005-04-24 jamoore add vijxtaDialog to dialog types
 */

public abstract class DialogManager {

    /**
     * The Map that holds the available managers of DialogManager
     * keyed by type. This ensures that only one instance of each
     * DialogManager type is creaed application wide
     */
    private static Map<Group, List<DialogManager>> managers = null;
    private static Map<Group, List<Dialog>> dialogs = null;
    private static Map<Group, List<CommandExecutor>> commandListeners = null;
    private static final Map<String, IDialogProvider> dialogProvider = new HashMap<String, IDialogProvider>();

    private String name = null;
    private String type = null;


    /**
     * Return the instance of the DialogManager to use, given a
     * type.
     *
     * @param type the type of DialogManager to return
     * @return the desired type of DialogManager
     */
    public static synchronized DialogManager getInstance(Group g, String name,
                                                         String type) {
        DialogManager dm = null;

        name = name != null ? name.trim() : null;
        type = type != null ? type.trim() : null;

        if (g != null &&
                name.length() > 0 &&
                type.length() > 0) {
            DialogManager tdm = null;

            if (PipeService.PropagateType.equals(type)) {
                tdm = new PropogateDialogManager(name, type);
            } else if (PipeService.UnicastType.equals(type)) {
                tdm = new UnicastDialogManager(name, type);
            } else if (PipeService.UnicastSecureType.equals(type)) {
                tdm = new UnicastSecureDialogManager(name, type);
            }

            if (managers != null) {
                List<DialogManager> ml = managers.get(g);
                int i = ml != null ? ml.indexOf(tdm) : -1;

                dm = i > -1 ? ml.get(i) : null;
            }

            if (dm == null) {
                dm = tdm;

                if (managers == null) {
                    managers = new HashMap<Group, List<DialogManager>>();
                }

                List<DialogManager> ml = managers.get(g);

                if (ml != null) {
                    if (!ml.contains(dm)) {
                        ml.add(dm);
                    }
                } else {
                    ml = new ArrayList<DialogManager>();

                    ml.add(dm);

                    managers.put(g, ml);
                }
            }
        }

        return dm;
    }

    public static Dialog getDialog(Class<BeamDialog> c, Group g, MyJXTA myjxta) {
        Dialog d = null;

        if (c != null &&
                Dialog.class.isAssignableFrom(c) &&
                g != null &&
                myjxta != null) {
            Dialog td = null;

            if (c == BeamDialog.class) {
                td = new BeamDialog(g, myjxta);
            }

            d = getDialog(g, td);
        }

        return d;
    }

    /**
     * Returns the Dialog associated with the specified pipe.
     *
     * @modified 2005-04-24 jamoore add vijxtaDialog to dialog types
     */
    public static Dialog getDialog(Class c, Group g, JxtaBiDiPipe pipe,
                                   MyJXTA myjxta) {
        Dialog d = null;

        if (c != null &&
                Dialog.class.isAssignableFrom(c) &&
                g != null &&
                pipe != null &&
                myjxta != null) {
            Dialog td = null;

            if (c == OneToOneCommandDialog.class) {
                td = new OneToOneCommandDialog(g, pipe, myjxta);
            } else {
                DialogManager.IDialogProvider provider = dialogProvider.get(c.getName());
                if (provider != null) {
                    td = provider.createDialog(g, pipe, myjxta);
                }

//            if (c.getName() == TicTacToeDialog.class.getName()) {
//                td = new TicTacToeDialog(g, pipe, myjxta);
            }

            d = getDialog(g, td);
        }

        return d;
    }


    /**
     * Returns the Dialog associated with the specified pipe advertisement.
     *
     * @modified 2005-04-24 jamoore add vijxtaDialog to dialog types
     */
    public static Dialog getDialog(Class c, Group g, PipeAdvertisement pa,
                                   MyJXTA myjxta) {
        Dialog d = null;

        if (c != null &&
                Dialog.class.isAssignableFrom(c) &&
                g != null &&
                pa != null &&
                myjxta != null) {
            Dialog td = null;

            if (c == OneToOneCommandDialog.class) {
                td = new OneToOneCommandDialog(g, pa, myjxta);
            } else {
                //classloader prevents true identity comparison
                DialogManager.IDialogProvider provider = dialogProvider.get(c.getName());
                if (provider != null) {
                    td = provider.createDialog(g, pa, myjxta);
                }
            }
            d = getDialog(g, td);
        }

        return d;
    }

    public static void installCommandListener(Class<OneToOneCommandDialog> c, Group g, JxtaBiDiPipe pipe,
                                              MyJXTA myjxta) {
        getCommandDialog(g, (OneToOneCommandDialog) getDialog(c, g, pipe, myjxta));
    }

    public static OneToOneCommandDialog getCommandDialog(Class<OneToOneCommandDialog> c, Group g, PipeAdvertisement pa,
                                                         MyJXTA myjxta) {
        return getCommandDialog(g, (OneToOneCommandDialog) getDialog(c, g, pa, myjxta));
    }

    public static void removeDialog(Group g, Dialog d) {
        if (dialogs != null &&
                dialogs.containsKey(g) &&
                d != null) {
            List<Dialog> dl = dialogs.get(g);
            int i = dl != null ? dl.indexOf(d) : -1;

            if (i > -1) {
                (dl.get(i)).close();
                dl.remove(i);

                if (dl.size() == 0) {
                    dl.clear();
                    dialogs.remove(g);

                    if (dialogs.size() == 0) {
                        dialogs.clear();

                        dialogs = null;
                    }
                }
            }
        }
    }

    public static void clearDialogs(Group g) {
        if (dialogs != null && dialogs.containsKey(g)) {
            ArrayList<Dialog> cl = new ArrayList<Dialog>(dialogs.get(g));

            for (Iterator<Dialog> d = cl.iterator(); d.hasNext();) {
                Object next = d.next();
                if (next instanceof Dialog) {
                    Dialog dialog = (Dialog) next;
                    removeDialog(g, dialog);
                } else {
                    //todo gonzo: why are there commandListeners in the list? the commander hashmap isnt used for anything....
//                    System.out.println("WARNING - there are commandListeners in the dialog list!");
                }
            }
        }
    }

    public static void removeCommander(Group g, CommandExecutor c) {
        if (commandListeners != null &&
                commandListeners.containsKey(g) &&
                c != null) {
            List<CommandExecutor> cl = commandListeners.get(g);
            int i = cl != null ? cl.indexOf(c) : -1;

            if (i > -1) {
                cl.remove(i);

                if (cl.size() == 0) {
                    cl.clear();
                    commandListeners.remove(g);

                    if (commandListeners.size() == 0) {
                        commandListeners.clear();

                        commandListeners = null;
                    }
                }
            }
        }
    }

    public static void clearCommanders(Group g) {
        if (commandListeners != null &&
                commandListeners.containsKey(g)) {
            List<CommandExecutor> cl = new ArrayList<CommandExecutor>(commandListeners.get(g));

            for (CommandExecutor aCl : cl) {
                removeCommander(g, aCl);
            }
        }
    }


    public static void clear(Group g) {
        clearDialogs(g);
        clearCommanders(g);
    }

    public DialogManager(String name, String type) {
        this.name = name;
        this.type = type;
    }

    /**
     * Get the PipeAdvertisement for the indicated PeerGroup
     *
     * @param pg the PeerGroup for which to create the advertisment
     */
    public abstract PipeAdvertisement getPipeAdv(PeerGroup pg);

    /**
     * Add a new DialogPipeListener
     *
     * @param pg       the PeerGroup in which the DialogPipeListener operates
     * @param listener the DialogPipeListener to add
     */
    public abstract void addPipeListener(PeerGroup pg,
                                         DialogPipeListener listener);

    /**
     * Removes a DialogPipeListener previously registered for
     * the indicated PeerGroup
     *
     * @param pg       the PeerGroup to which the DialogPipeListener belongs
     * @param listener the listener to remove
     */
    public abstract void removePipeListener(PeerGroup pg,
                                            DialogPipeListener listener);

    public abstract void clearPipeListeners(PeerGroup pg);

    public String getDialogName() {
        return this.name;
    }

    public String getDialogType() {
        return this.type;
    }

    public boolean equals(Object o) {
        return DialogManager.class.isAssignableFrom(o.getClass()) &&
                ((DialogManager) o).getDialogName().equals(getDialogName()) &&
                ((DialogManager) o).getDialogType().equals(getDialogType());
    }

    protected String getName() {
        return this.name;
    }

    protected String getType() {
        return this.type;
    }

    public static List<Dialog> getDialogsForGroup(Group group) {
        if (dialogs == null) {
            dialogs = new HashMap<Group, List<Dialog>>();
        }

        return dialogs.get(group);
    }

    private static void addDialogToGroup(Group group, Dialog dialog) {
        List<Dialog> list = getDialogsForGroup(group);
        if (list == null) {
            list = new ArrayList<Dialog>();
            dialogs.put(group, list);
        }
        if (!list.contains(dialog)) {
            list.add(dialog);
        }
    }

    private static Dialog getDialog(Group group, Dialog dialogTemplate) {
        Dialog d;
        List<Dialog> list = getDialogsForGroup(group);

        int i = list != null ? list.indexOf(dialogTemplate) : -1;

        d = i > -1 ? list.get(i) : null;

        if (d == null || !d.isConnected()) {
            d = dialogTemplate;
            d.activate();
            if (d.isConnected()){
                addDialogToGroup(group, d);
            } else {
                //template is not connected
            }
        }



        return d;
    }

    private static OneToOneCommandDialog getCommandDialog(Group g, OneToOneCommandDialog d) {
        CommandExecutor c = null;
        CommandExecutor commanExecutorTemplate = new CommandExecutor(d);

        //nano: oh my.... (i assume: the intention is to reuse a commandListener if there is already one)
        if (commandListeners != null) {
            List<CommandExecutor> cl = commandListeners.get(g);
            int i = cl != null ? cl.indexOf(commanExecutorTemplate) : -1;

            c = i > -1 ? cl.get(i) : null;
        }

        if (c == null) {
            //not there, we are using the template
            c = commanExecutorTemplate;

            if (commandListeners == null) {
                commandListeners = new HashMap<Group, List<CommandExecutor>>();
            }

            List<CommandExecutor> cl = commandListeners.get(g);

            if (cl != null) {
                if (!cl.contains(c)) {
                    cl.add(c);
                }
            } else {
                cl = new ArrayList<CommandExecutor>();

                cl.add(c);

                commandListeners.put(g, cl);
            }
        }

        //if the CommandExecutor==DialogListener is not yet registerd at the CommandDialog --> register it!
        if (!c.getDialog().getListeners().contains(c)) {
            c.getDialog().addListener(c);
        }
        return c.getDialog();
    }

    public static void registerDialogProvider(Class p_class, IDialogProvider p_provider) {
        Object oldProvider = dialogProvider.get(p_class.getName());
        if (oldProvider != null) {
            throw new IllegalArgumentException("there is already a provider" + oldProvider + " for this class");
        }
        dialogProvider.put(p_class.getName(), p_provider);
    }

    public static void removeDialogProvider(Class p_class) {
        Object oldProvider = dialogProvider.get(p_class.getName());
        if (oldProvider == null) {
            throw new IllegalArgumentException("there is no provider installed for class" + p_class);
        }
        dialogProvider.remove(p_class.getName());
    }

    public interface IDialogProvider {
        Dialog createDialog(Group p_g, PipeAdvertisement p_pa, MyJXTA p_myjxta);

        Dialog createDialog(Group p_g, JxtaBiDiPipe p_pipe, MyJXTA p_myjxta);
    }
}
